%%%-------------------------------------------------------------------
%% @doc chat_channel_worker public API
%% @end
%%%-------------------------------------------------------------------

-module(chat_channel_worker).
-behaviour(gen_server).

%% --------------------------------------------------------------------
%% Include files
%% --------------------------------------------------------------------

%%-include("debug.hrl").
-define(DEBUG(Format, Args),  io:format("~n-- " ++ Format ++ "~n", Args)).
-define(DEBUG(Arg),  io:format("~n-- ~p~n", [Arg])).


-define(CHAT_MSGID_BASE, 1).
-define(CHAT_MSGID_MAX,  99999999).
%% --------------------------------------------------------------------
%% External exports
%% --------------------------------------------------------------------
-export([ start_link/1
        , init/1
        , handle_call/3
        , handle_cast/2
        , handle_info/2
        , terminate/2
        , code_change/3
        ]).

-export([ 
        ]).

%% ====================================================================
-record(state, { msg_max
               , fetch_amount
               , channel
               , msg_id
               }).

%% ====================================================================
%% API
%% ====================================================================



%% ====================================================================
%% internal functions
%% ====================================================================
add_subscriber(Channel, Subscriber) ->
    SubscriberList = get_subscriber_list(Channel),
    case lists:member(Subscriber, SubscriberList) of
        true -> ignore;
        false ->
            erlang:monitor(process, Subscriber),
            set_subscriber_list(Channel, [Subscriber | SubscriberList])
    end,
    ok.


del_subscriber(Channel, Subscriber) ->
    SubscriberList = lists:delete(Subscriber, get_subscriber_list(Channel)),
    set_subscriber_list(Channel, SubscriberList),
    ok.


%% ====================================================================
get_subscriber_list(Channel) ->
    case erlang:get({channel, Channel}) of
        undefined -> [];
        List -> List
    end.

set_subscriber_list(Channel, SubscriberList) ->
    erlang:put({channel, Channel}, SubscriberList).

%% ====================================================================
msg_id(_Channel, Id) when Id < ?CHAT_MSGID_MAX -> Id;
msg_id(Channel, _) ->
    Data0 = chat_queue:get_queue(Channel),
    {Id, Data1} = chat_queue:reset_queue(Data0, ?CHAT_MSGID_BASE),
    chat_queue:set_queue(Channel, Data1),
    Id + 1.

%% ====================================================================
%% Behavioural functions
%% ====================================================================
do_init({Config, Channel}) ->
    MsgMax = maps:get(chat_save_message_max, Config, 1000),
    FetchAmount = maps:get(chat_fetch_message_amount, Config, 10),
    {ok, #state{ msg_max = MsgMax
               , fetch_amount = FetchAmount
               , channel = Channel
               , msg_id = ?CHAT_MSGID_BASE
               }}.

%%--------------------------------------------------------------------

do_call(Request, _From, State) ->
    ?DEBUG("chat_channel_worker call:~p", [Request]),
    {reply, ok, State}.

%%--------------------------------------------------------------------
do_cast({From, subscribe, Subscriber}, State) ->
    add_subscriber(State#state.channel, Subscriber),
    gen_server:reply(From, ok),
    {noreply, State};

do_cast({From, unsubscribe, Subscriber}, State) ->
    del_subscriber(State#state.channel, Subscriber),
    gen_server:reply(From, ok),
    {noreply, State};

do_cast({From, talk, Message}, State) ->
    gen_server:reply(From, ok),

    MsgId = msg_id(State#state.channel, State#state.msg_id),
    Data0 = chat_queue:get_queue(State#state.channel),
    Data1 = chat_queue:in(Data0, {MsgId, Message}, State#state.msg_max),
    chat_queue:set_queue(State#state.channel, Data1),

    [Pid ! {chat, {MsgId, Message}} || Pid <- get_subscriber_list(State#state.channel)],
    {noreply, State#state{msg_id = MsgId + 1}};

do_cast({From, fetch, MsgId}, State) ->
    Data = chat_queue:get_queue(State#state.channel),
    spawn(fun() -> gen_server:reply(From, chat_queue:fetch(Data, MsgId, State#state.fetch_amount)) end),
    {noreply, State};

do_cast(Request, State) ->
    ?DEBUG("chat_channel_worker cast:~p", [Request]),
    {noreply, State}.

%%--------------------------------------------------------------------
do_info({'DOWN', _Ref, process, Subscriber, _Reason}, State) ->
    del_subscriber(State#state.channel, Subscriber),
    {noreply, State};

do_info(Info, State) ->
    ?DEBUG("chat_channel_worker info:~p", [Info]),
    {noreply, State}.

%%--------------------------------------------------------------------
do_terminate(_Reason, _State) ->
    ?DEBUG("do_terminate:~p ~p", [?MODULE, _Reason]),
    ok.

%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link(Args :: term()) ->
    {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(Args) ->
    gen_server:start_link(?MODULE, Args, []).

%% ====================================================================
%% Behavioural functions
%% ====================================================================

%% init/1
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:init-1">gen_server:init/1</a>
-spec init(Args :: term()) -> Result when
    Result :: {ok, State}
            | {ok, State, Timeout}
            | {ok, State, hibernate}
            | {stop, Reason :: term()}
            | ignore,
    State :: term(),
    Timeout :: non_neg_integer() | infinity.
%% ====================================================================
init(Args) ->
    do_init(Args).


%% handle_call/3
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:handle_call-3">gen_server:handle_call/3</a>
-spec handle_call(Request :: term(), From :: {pid(), Tag :: term()}, State :: term()) -> Result when
    Result :: {reply, Reply, NewState}
            | {reply, Reply, NewState, Timeout}
            | {reply, Reply, NewState, hibernate}
            | {noreply, NewState}
            | {noreply, NewState, Timeout}
            | {noreply, NewState, hibernate}
            | {stop, Reason, Reply, NewState}
            | {stop, Reason, NewState},
    Reply :: term(),
    NewState :: term(),
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ====================================================================
handle_call(Request, From, State) ->
    try
        do_call(Request, From, State)
    catch
        Type:Reason:Stack ->
            ?DEBUG("handle_call error:~n"
                         "Request:~p, From:~p, State:~p~n"
                         "Type:~p, Reason:~p",
                         [Request, From, State, Type, Reason]),
            ?DEBUG("stacktrace:~p",[Stack]),
            {reply, fail, State}
    end.


%% handle_cast/2
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:handle_cast-2">gen_server:handle_cast/2</a>
-spec handle_cast(Request :: term(), State :: term()) -> Result when
    Result :: {noreply, NewState}
            | {noreply, NewState, Timeout}
            | {noreply, NewState, hibernate}
            | {stop, Reason :: term(), NewState},
    NewState :: term(),
    Timeout :: non_neg_integer() | infinity.
%% ====================================================================
handle_cast(Msg, State) ->
    try
        do_cast(Msg, State)
    catch
        Type:Reason:Stack ->
            ?DEBUG("handle_cast error:~n"
                         "Msg:~p, State:~p~n"
                         "Type:~p, Reason:~p",
                         [Msg, State, Type, Reason]),
            ?DEBUG("stacktrace:~p",[Stack]),
            {noreply, State}
    end.


%% handle_info/2
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:handle_info-2">gen_server:handle_info/2</a>
-spec handle_info(Info :: timeout | term(), State :: term()) -> Result when
    Result :: {noreply, NewState}
            | {noreply, NewState, Timeout}
            | {noreply, NewState, hibernate}
            | {stop, Reason :: term(), NewState},
    NewState :: term(),
    Timeout :: non_neg_integer() | infinity.
%% ====================================================================
handle_info(Info, State) ->
    try
        do_info(Info, State)
    catch
        Type:Reason:Stack ->
            ?DEBUG("handle_info error:~n"
                         "Info:~p, State:~p~n"
                         "Type:~p, Reason:~p",
                         [Info, State, Type, Reason]),
            ?DEBUG("stacktrace:~p",[Stack]),
            {noreply, State}
    end.


%% terminate/2
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:terminate-2">gen_server:terminate/2</a>
-spec terminate(Reason, State :: term()) -> Any :: term() when
    Reason :: normal
            | shutdown
            | {shutdown, term()}
            | term().
%% ====================================================================
terminate(Reason, State) ->
    do_terminate(Reason, State).


%% code_change/3
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:code_change-3">gen_server:code_change/3</a>
-spec code_change(OldVsn, State :: term(), Extra :: term()) -> Result when
    Result :: {ok, NewState :: term()} | {error, Reason :: term()},
    OldVsn :: Vsn | {down, Vsn},
    Vsn :: term().
%% ====================================================================
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.
